package io.hepu.robotize.security;

import io.hepu.robotize.IContext;
import io.hepu.robotize.auth.JRedis;
import io.hepu.robotize.auth.Jwts;
import io.hepu.robotize.enums.IStatus;
import io.hepu.robotize.enums.Permissions;
import io.hepu.robotize.exception.AuthGrantException;
import io.hepu.robotize.model.Permission;
import io.hepu.robotize.model.Role;
import io.hepu.robotize.model.User;
import io.hepu.robotize.service.ISysService;
import io.hepu.robotize.util.Limits;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class ShiroJdbcRealm extends AuthorizingRealm {
    private static final Logger LOG = LoggerFactory.getLogger(ShiroJdbcRealm.class);

    @Resource
    @Lazy
    private ISysService sysService;

    @Override
    public void setName(String name) {
        super.setName("ShiroJdbcRealm");
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    public boolean isPermitted(PrincipalCollection principals, String permission) {
        return super.isPermitted(principals, permission);
    }

    @Override
    public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
        return  super.hasRole(principals, roleIdentifier);
    }

    /*-------------------------------------
    |              用 户 认 证              |
    =======================================*/
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authentication) {
        LOG.debug("|          Shiro Certification              |");
        String token = authentication.getPrincipal().toString();
        String userId = Jwts.getSubject(token);
        User user = sysService.findUserById(userId);
        // + -------------------------
        // + |  USER STATUS CHECKING |
        // + -------------------------
        AuthGrantGuard.verify(user);
        String timestampKey = IContext.getJwtRefreshKey(userId);
        if (!JRedis.exists(timestampKey)) {
            throw AuthGrantException.of(IStatus.AUTHENTICATED_EXPIRED);
        }
        if (Jwts.verify(token)) {
            // Comparing the timestamps, the token is valid if they are consistently.
            if (Limits.equals(Jwts.getSigningAt(token), JRedis.string(timestampKey))) {
                LOG.debug("Authenticated successful => {}:{}", userId, token);
                /* ***************************************************************************************************
                 * The first parameter is the principal（身份）, which will be encapsulated as PrincipalCollection
                 * at the time of authorization getPrimaryPrincipal() is used, so the jwt content must be returned.
                 *
                 * The second parameter is the credentials（证明）, that is, the password.
                 * For the subsequent verification to pass, it needs to be the same as the content in the token.
                 *
                 * The third parameter is the domain name（领域）
                 * ***************************************************************************************************/
                return new SimpleAuthenticationInfo(userId, token, this.getName());
            }
        }
        throw AuthGrantException.of(IStatus.AUTHENTICATED_FAILURE);
    }

    /*-------------------------------------
    |              用 户 授 权              |
    =======================================*/
    // 当需要鉴权时才会触发该方法：checkPermission, checkRole, isPermitted etc.
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        LOG.debug("|           Shiro Authorization             |");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        String userId = principals.getPrimaryPrincipal().toString();

        // 角色集合
        List<Role> roles = sysService.findRolesByUserId(userId);
        List<String> roleNames = roles.stream().map(Role::getCode).collect(Collectors.toList());
        simpleAuthorizationInfo.addRoles(roleNames);

        // 权限集合
        List<Permission> permissions = sysService.findPermsByUserId(userId);
        List<String> privileges = permissions.stream().filter(perm -> perm.getType() == Permissions.ACTION)
                .map(Permission::getCode).collect(Collectors.toList());
        simpleAuthorizationInfo.addStringPermissions(privileges);

        return simpleAuthorizationInfo;
    }

    /*----------------------
    |      清 除 缓 存       |
    ========================*/
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    /*---------------------
     *  清理指定用户授权缓存
     ======================*/
    public void clearCachedAuthorization(Object principal) {
        SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, this.getName());
        this.clearCachedAuthorizationInfo(principals);
    }

    /*---------------------
     *  清理所有用户授权缓存
     =====================*/
    public void clearCachedAuthorization() {
        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
        if (cache != null) {
            cache.clear();
        }
    }

}
